#include <limits>
#include <map>
#include <utility>
#include <string>
#include <vector>
#include <set>

#include <stdio.h>

#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/stubs/common.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/stubs/substitute.h>

#include "MyOptions.pb.h"
#include "sql_generator.h"

namespace google
{
namespace protobuf
{
namespace compiler
{
namespace sql
{

namespace
{
std::string trim(std::string& s, const std::string& drop = " ")
{
	std::string r = s.erase(s.find_last_not_of(drop) + 1);
	return r.erase(0, r.find_first_not_of(drop));
}

// Returns a copy of |filename| with any trailing ".protodevel" or ".proto
// suffix stripped.
// TODO(robinson): Unify with copy in compiler/cpp/internal/helpers.cc.
string StripProto(const string& filename)
{
	const char* suffix =
			HasSuffixString(filename, ".protodevel") ? ".protodevel" : ".proto";
	return StripSuffixString(filename, suffix);
}

string StringifyFieldType(const FieldDescriptor& field)
{
	static map<FieldDescriptor::Type, string> m;

	m[FieldDescriptor::TYPE_DOUBLE] = "DOUBLE";
	m[FieldDescriptor::TYPE_FLOAT] = "FLOAT";
	m[FieldDescriptor::TYPE_INT64] = "BIGINT";
	m[FieldDescriptor::TYPE_UINT64] = "BIGINT UNSIGNED";
	m[FieldDescriptor::TYPE_INT32] = "INT";
	m[FieldDescriptor::TYPE_FIXED64] = "";
	m[FieldDescriptor::TYPE_FIXED32] = "";
	m[FieldDescriptor::TYPE_BOOL] = "";
	m[FieldDescriptor::TYPE_STRING] = "MEDIUMTEXT";
	m[FieldDescriptor::TYPE_GROUP] = "";
	m[FieldDescriptor::TYPE_MESSAGE] = "MEDIUMBLOB";
	m[FieldDescriptor::TYPE_BYTES] = "";
	m[FieldDescriptor::TYPE_UINT32] = "INT UNSIGNED";
	m[FieldDescriptor::TYPE_ENUM] = "";
	m[FieldDescriptor::TYPE_SFIXED32] = "";
	m[FieldDescriptor::TYPE_SFIXED64] = "";
	m[FieldDescriptor::TYPE_SINT32] = "";
	m[FieldDescriptor::TYPE_SINT64] = "";

	return m[field.type()];
}

string StringifyDefaultValue(const FieldDescriptor& field)
{
	//if (field.is_repeated())
	//{
	//    return "[]";
	//}

	switch (field.cpp_type())
	{
	case FieldDescriptor::CPPTYPE_INT32:
		return SimpleItoa(field.default_value_int32());
	case FieldDescriptor::CPPTYPE_UINT32:
		return SimpleItoa(field.default_value_uint32());
	case FieldDescriptor::CPPTYPE_INT64:
		return SimpleItoa(field.default_value_int64());
	case FieldDescriptor::CPPTYPE_UINT64:
		return SimpleItoa(field.default_value_uint64());
	case FieldDescriptor::CPPTYPE_DOUBLE:
	{
		double value = field.default_value_double();
		if (value == numeric_limits<double>::infinity())
		{
			// Python pre-2.6 on Windows does not parse "inf" correctly.  However,
			// a numeric literal that is too big for a double will become infinity.
			return "1e10000";
		}
		else if (value == -numeric_limits<double>::infinity())
		{
			// See above.
			return "-1e10000";
		}
		else if (value != value)
		{
			// infinity * 0 = nan
			return "(1e10000 * 0)";
		}
		else
		{
			return SimpleDtoa(value);
		}
	}
	case FieldDescriptor::CPPTYPE_FLOAT:
	{
		float value = field.default_value_float();
		if (value == numeric_limits<float>::infinity())
		{
			// Python pre-2.6 on Windows does not parse "inf" correctly.  However,
			// a numeric literal that is too big for a double will become infinity.
			return "1e10000";
		}
		else if (value == -numeric_limits<float>::infinity())
		{
			// See above.
			return "-1e10000";
		}
		else if (value != value)
		{
			// infinity - infinity = nan
			return "(1e10000 * 0)";
		}
		else
		{
			return SimpleFtoa(value);
		}
	}
	case FieldDescriptor::CPPTYPE_BOOL:
		return field.default_value_bool() ? "True" : "False";
	case FieldDescriptor::CPPTYPE_ENUM:
		return SimpleItoa(field.default_value_enum()->number());
	case FieldDescriptor::CPPTYPE_STRING:
		if (field.type() == FieldDescriptor::TYPE_STRING)
		{
			return "unicode(\"" + CEscape(field.default_value_string())
					+ "\", \"utf-8\")";
		}
		else
		{
			return "\"" + CEscape(field.default_value_string()) + "\"";
		}
	case FieldDescriptor::CPPTYPE_MESSAGE:
		return "None";
	}
	// (We could add a default case above but then we wouldn't get the nice
	// compiler warning when a new type is added.)
	GOOGLE_LOG(FATAL) << "Not reached.";
	return "";
}

string SqlFileName(const string& filename)
{
	string basename = StripProto(filename);
	StripString(&basename, "-", '_');
	StripString(&basename, "/", '.');
	return basename + ".sql";
}

} // namespace

SQLGenerator::SQLGenerator() :
		file_(NULL)
{
}

SQLGenerator::~SQLGenerator()
{
}

bool SQLGenerator::Generate(const FileDescriptor* file, const string& parameter,
		GeneratorContext* context, string* error) const
{
	// Completely serialize all Generate() calls on this instance.  The
	// thread-safety constraints of the CodeGenerator interface aren't clear so
	// just be as conservative as possible.  It's easier to relax this later if
	// we need to, but I doubt it will be an issue.
	// TODO(kenton):  The proper thing to do would be to allocate any state on
	//   the stack and use that, so that the SQLGenerator class itself does not need
	//   to have any mutable members.  Then it is implicitly thread-safe.
	MutexLock lock(&mutex_);
	file_ = file;
	string filename = SqlFileName(file->name());

	FileDescriptorProto fdp;
	file_->CopyTo(&fdp);
	fdp.SerializeToString(&file_descriptor_serialized_);

	scoped_ptr < io::ZeroCopyOutputStream > output(context->Open(filename));
	GOOGLE_CHECK(output.get());
	io::Printer printer(output.get(), '$');
	printer_ = &printer;

	printer_->Print(
			"# Created my protobuf sql plugin written by hgc, do not edit!!");

	PrintSql(error);

	return !printer.failed();
}

void SQLGenerator::PrintSql(string* error) const
{
	for (int i = 0; i < file_->message_type_count(); ++i)
	{
		PrintCreateTableSql(*file_->message_type(i), error);
	}
}

void SQLGenerator::PrintCreateTableSql(const Descriptor& message_descriptor,
		string* error) const
{
	MyMsgOptions oMyMsgOptions = message_descriptor.options().GetExtension(
			stMsgOptions);

	bool bTable = oMyMsgOptions.btable();
	if (!bTable)
	{
		return;
	}

	printer_->Print("\n");

	string strPrimaryKey = oMyMsgOptions.strprimarykey();

	unsigned int i;

	// check primary key list
	vector<string> pk_vector;
	SplitStringUsing(strPrimaryKey, ",", &pk_vector);

	set<string> pk_set;

	for (i = 0; i < pk_vector.size(); ++i)
	{
		string tmp = trim(pk_vector[i]);

		if (!pk_set.insert(tmp).second)
		{
			char szError[1024] = "";
			sprintf(szError, "Repetitive primary key: %s", tmp.c_str());
			*error = szError;
			return;
		}
	}

	set<string> field_set;
	for (i = 0; i < message_descriptor.field_count(); ++i)
	{
		field_set.insert(message_descriptor.field(i)->name());
	}

	set<string>::iterator iter1, iter2;

	for (iter1 = pk_set.begin(); iter1 != pk_set.end(); ++iter1)
	{
		iter2 = field_set.find(*iter1);
		if (iter2 == field_set.end())
		{
			char szError[1024] = "";
			sprintf(szError, "Invalid primary key: %s", iter1->c_str());
			*error = szError;
			return;
		}
	}

	int i32SplitTableFactor = oMyMsgOptions.i32splittablefactor();

	for (i = 0; i < i32SplitTableFactor; ++i)
	{
		printer_->Print("\n");

		printer_->Print("CREATE TABLE IF NOT EXISTS $name$_$postfix$ (", "name",
				message_descriptor.name(), "postfix", SimpleItoa(i));

		PrintFieldsInTable(message_descriptor, &Descriptor::field_count,
				&Descriptor::field);

		printer_->Print("\n");
		printer_->Print(
				"PRIMARY KEY($pk$)) ENGINE=InnoDB DEFAULT CHARSET=latin1;",
				"pk", strPrimaryKey);
	}

	return;
}

void SQLGenerator::PrintFieldsInTable(const Descriptor& message_descriptor,
		int(Descriptor::*CountFn)() const,
		const FieldDescriptor* (Descriptor::*GetterFn)(int) const) const
{
	printer_->Indent();

	for (int i = 0; i < (message_descriptor.*CountFn)(); ++i)
	{
		PrintTableField(*(message_descriptor.*GetterFn)(i), false);
	}

	printer_->Outdent();
}

void SQLGenerator::PrintTableField(const FieldDescriptor& field,
		bool is_extension) const
{
	MyFieldOptions oMyFieldOptions = field.options().GetExtension(
			stFieldOptions);
	bool bNotNull = oMyFieldOptions.bnotnull();

	printer_->Print("\n");

	printer_->Print("$name$", "name", field.name());
	printer_->Print(" $type$", "type", StringifyFieldType(field));

	if (bNotNull)
	{
		printer_->Print(" $not_null$", "not_null", "NOT NULL");
	}

	if (field.has_default_value())
	{
		printer_->Print(" DEFAULT '$default_value$'", "default_value",
				StringifyDefaultValue(field));
	}

	printer_->Print(",");

	return;
}

} // namespace sql
} // namespace compiler
} // namespace protobuf
} // namespace google
